﻿using UnityEngine;
using System.Collections;

/// <summary>
/// 수학 함수 클래스.
/// </summary>
public static class wsMath
{
    /// <summary>
    /// 절대값 구하기, int.
    /// </summary>
    /// <param name="value">절대값을 구하려는 값</param>
    /// <returns>절대값</returns>
    public static int Abs(int value)
    {
        return System.Math.Abs(value);
    }

    /// <summary>
    /// 절대값 구하기, float
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static float Abs(float value)
    {
        return UnityEngine.Mathf.Abs(value);
    }

    /// <summary>
    /// 상대적인 크기 구하기.
    /// </summary>
    /// <param name="original_size">원래 크기</param>
    /// <param name="original_ratio">원래 비율</param>
    /// <param name="relative_ratio">상대 비율</param>
    /// <returns>상대 크기(relative_size)</returns>
    public static int GetRatioSize(int original_size, int original_ratio, int relative_ratio)
    {
        return (int)(original_size * relative_ratio / (float)original_ratio);
    }

    /// <summary>
    /// 십진수의 자리수 구하기. 예) 1357 -> 4자리.
    /// </summary>
    /// <param name="num">십진수</param>
    /// <returns>자리수</returns>
    public static int GetDigit(int num)
    {
        int digit = 0;

        do
        {
            digit++;
            num = num / 10;
        }
        while (num > 0);

        return digit;
    }

    /// <summary>
    /// 해당 자리수의 숫자 구하기. 예) (1357, 2) -> 1357의 둘째자리의 수는 5이다.
    /// </summary>
    /// <param name="num">1357</param>
    /// <param name="digit">2</param>
    /// <returns>5</returns>
    public static int GetNumByDigit(int num, int digit)
    {
        int digitLength = GetDigit(num);
        if (digit > digitLength || digit <= 0) return -1;

        // get the order num.
        num = num / (int)Mathf.Pow(10, digit - 1);
        num = num % 10;

        return num;
    }

    /// <summary>
    /// 해당 순서의 숫자 구하기. 예 (1357, 2) -> 1357의 두번째 숫자는 3이다.
    /// </summary>
    /// <param name="num">1357</param>
    /// <param name="order">2</param>
    /// <returns>3</returns>
    public static int GetNumByOrder(int num, int order)
    {
        return GetNumByDigit(num, GetDigit(num) - (order - 1));
    }

    /// <summary>
    /// 블럭에서 인덱스 번호에 대한 좌표값 구하는 함수.
    /// </summary>
    /// <param name="index">블럭 인덱스</param>
    /// <param name="startPos">블럭 시작위치</param>
    /// <param name="cellWidth">블럭 가로크기</param>
    /// <param name="cellHeight">블럭 세로크기</param>
    /// <param name="rowCount">라인당 블럭개수</param>
    /// <returns>블럭 좌표</returns>
    public static Vector3 GetBlockPos(int index, Vector3 startPos, float cellWidth, float cellHeight, int rowCount)
    {
        int px = index % rowCount;
        int py = index / rowCount;

        Vector3 result = startPos + new Vector3(px * cellWidth, py * cellHeight, 0);

        return result;
    }

	/// <summary>
	/// 목표지점까지 부드럽게 이동하기.
	/// </summary>
	/// <param name="transform">object to move</param>
	/// <param name="targetPos">target position</param>
	/// <param name="damping">time for arrive</param>
	/// <param name="useLocal">if checked, relative to the parent transform</param>
	public static void MoveSmooth(Transform transform, Vector3 targetPos, float damping, bool useLocal)
	{
		if (useLocal)
			transform.localPosition = Vector3.Lerp(transform.localPosition, targetPos, damping * Time.deltaTime);
		else
			transform.position = Vector3.Lerp(transform.position, targetPos, damping * Time.deltaTime);
	}

    /// <summary>
    /// 이동 후 회전 함수.
    /// </summary>
    /// <param name="target"></param>
    /// <param name="move"></param>
    /// <param name="axis"></param>
    /// <param name="angle"></param>
    public static void MoveAndRotate(GameObject target, Vector3 move, Vector3 axis, float angle)
    {
        Vector3 pos = target.transform.position;
        target.transform.Translate(move);
        target.transform.RotateAround(pos, axis, angle);
    }

    /// <summary>
    /// 축을 기준으로 회전.
    /// </summary>
    /// <param name="target"></param>
    /// <param name="axisPos"></param>
    /// <param name="axis"></param>
    /// <param name="angle"></param>
    /// <returns></returns>
    public static Vector3 RotateAround(Vector3 target, Vector3 axisPos, Vector3 axis, float angle)
    {
        GameObject temp = new GameObject();
        temp.transform.position = target;
        temp.transform.RotateAround(axisPos, axis, angle);

        return temp.transform.position;
    }

    /// <summary>
    /// Get Angle Between Three Point with no sign. (p2->p1 vector and p2->p3 vector)
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <param name="p3"></param>
    /// <returns></returns>
    public static float Angle(Vector3 p1, Vector3 p2, Vector3 p3)
    {
        // Vector from p2 to p1.
        Vector3 v1 = p1 - p2;
        // Vector from p2 to p3.
        Vector3 v2 = p3 - p2;

        return Angle(v1, v2);
    }

    /// <summary>
    /// Get Angle Between Two Vector with no sign.
    /// </summary>
    /// <param name="v1"></param>
    /// <param name="v2"></param>
    /// <returns></returns>
    public static float Angle(Vector3 v1, Vector3 v2)
    {
        // a.b = |a||b|cos@, cos@ = arccos(a.b/|a||b|)
        float v1DOTv2 = Vector3.Dot(v1, v2);
        float ABSv1XABSv2 = v1.magnitude * v2.magnitude;

        return Mathf.Acos(v1DOTv2 / ABSv1XABSv2) * Mathf.Rad2Deg;
    }

    /// <summary>
    /// Get Angle Between Three Point with sign. (p2->p1 vector and p2->p3 vector)
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <param name="p3"></param>
    /// <param name="n"></param>
    /// <returns></returns>
    public static float AngleSigned(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 n)
    {
        // Vector from p2 to p1.
        Vector3 v1 = p1 - p2;
        // Vector from p2 to p3.
        Vector3 v2 = p3 - p2;

        return AngleSigned(v1, v2, n);
    }

    /// <summary>
    /// Get Angle Between Two Vector with sign.
    /// </summary>
    /// <param name="v1"></param>
    /// <param name="v2"></param>
    /// <param name="n"></param>
    /// <returns></returns>
    public static float AngleSigned(Vector3 v1, Vector3 v2, Vector3 n)
    {
        return Mathf.Atan2(
            Vector3.Dot(n, Vector3.Cross(v1, v2)),
            Vector3.Dot(v1, v2)) * Mathf.Rad2Deg;
    }
}

public class Mathfx
{
    public static float Hermite(float start, float end, float value)
    {
        return Mathf.Lerp(start, end, value * value * (3.0f - 2.0f * value));
    }

    public static float Sinerp(float start, float end, float value)
    {
        return Mathf.Lerp(start, end, Mathf.Sin(value * Mathf.PI * 0.5f));
    }

    public static float Coserp(float start, float end, float value)
    {
        return Mathf.Lerp(start, end, 1.0f - Mathf.Cos(value * Mathf.PI * 0.5f));
    }

    public static float Berp(float start, float end, float value)
    {
        value = Mathf.Clamp01(value);
        value = (Mathf.Sin(value * Mathf.PI * (0.2f + 2.5f * value * value * value)) * Mathf.Pow(1f - value, 2.2f) + value) * (1f + (1.2f * (1f - value)));
        return start + (end - start) * value;
    }

    public static float SmoothStep(float x, float min, float max)
    {
        x = Mathf.Clamp(x, min, max);
        float v1 = (x - min) / (max - min);
        float v2 = (x - min) / (max - min);
        return -2 * v1 * v1 * v1 + 3 * v2 * v2;
    }

    public static float Lerp(float start, float end, float value)
    {
        return ((1.0f - value) * start) + (value * end);
    }

    public static Vector3 NearestPoint(Vector3 lineStart, Vector3 lineEnd, Vector3 point)
    {
        Vector3 lineDirection = Vector3.Normalize(lineEnd - lineStart);
        float closestPoint = Vector3.Dot((point - lineStart), lineDirection) / Vector3.Dot(lineDirection, lineDirection);
        return lineStart + (closestPoint * lineDirection);
    }

    public static Vector3 NearestPointStrict(Vector3 lineStart, Vector3 lineEnd, Vector3 point)
    {
        Vector3 fullDirection = lineEnd - lineStart;
        Vector3 lineDirection = Vector3.Normalize(fullDirection);
        float closestPoint = Vector3.Dot((point - lineStart), lineDirection) / Vector3.Dot(lineDirection, lineDirection);
        return lineStart + (Mathf.Clamp(closestPoint, 0.0f, Vector3.Magnitude(fullDirection)) * lineDirection);
    }
    public static float Bounce(float x)
    {
        return Mathf.Abs(Mathf.Sin(6.28f * (x + 1f) * (x + 1f)) * (1f - x));
    }

    // test for value that is near specified float (due to floating point inprecision)
    // all thanks to Opless for this!
    public static bool Approx(float val, float about, float range)
    {
        return ((Mathf.Abs(val - about) < range));
    }

    // test if a Vector3 is close to another Vector3 (due to floating point inprecision)
    // compares the square of the distance to the square of the range as this 
    // avoids calculating a square root which is much slower than squaring the range
    public static bool Approx(Vector3 val, Vector3 about, float range)
    {
        return ((val - about).sqrMagnitude < range * range);
    }

    /*
      * CLerp - Circular Lerp - is like lerp but handles the wraparound from 0 to 360.
      * This is useful when interpolating eulerAngles and the object
      * crosses the 0/360 boundary.  The standard Lerp function causes the object
      * to rotate in the wrong direction and looks stupid. Clerp fixes that.
      */
    public static float Clerp(float start, float end, float value)
    {
        float min = 0.0f;
        float max = 360.0f;
        float half = Mathf.Abs((max - min) / 2.0f);//half the distance between min and max
        float retval = 0.0f;
        float diff = 0.0f;

        if ((end - start) < -half)
        {
            diff = ((max - start) + end) * value;
            retval = start + diff;
        }
        else if ((end - start) > half)
        {
            diff = -((max - end) + start) * value;
            retval = start + diff;
        }
        else retval = start + (end - start) * value;

        // Debug.Log("Start: "  + start + "   End: " + end + "  Value: " + value + "  Half: " + half + "  Diff: " + diff + "  Retval: " + retval);
        return retval;
    }

}